package tech.blueglacier.disposition;

import com.sun.mail.util.PropUtil;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.util.CharsetUtil;
import tech.blueglacier.configuration.AppConfig;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

public class ContentDispositionDecoder {

    private static final boolean DECODE_PARAMETERS_STRICT = PropUtil.getBooleanSystemProperty("mail.mime.decodeparameters.strict", false);

    private static ContentDispositionHeaderValue decodeContentDisposition(String headerValue) throws MimeException {
        ContentDispositionHeaderValue contentDispositionHeaderValue = new ContentDispositionHeaderValue();
        contentDispositionHeaderValue.setValue(headerValue);
        try {
            int i = headerValue.indexOf('\'');
            if (i <= 0) {
                if (DECODE_PARAMETERS_STRICT) {
                    throw new MimeException("Missing charset in encoded value: " + headerValue);
                }
                return contentDispositionHeaderValue;
            }

            String charset = headerValue.substring(0, i);
            charset = AppConfig.getFallbackCharset(charset);
            if (CharsetUtil.lookup(charset) == null) {
                return contentDispositionHeaderValue;
            }

            int li = headerValue.indexOf('\'', i + 1);
            if (li < 0) {
                if (DECODE_PARAMETERS_STRICT) {
                    throw new MimeException("Missing language in encoded value: " + headerValue);
                }
                return contentDispositionHeaderValue;
            }
            headerValue = headerValue.substring(li + 1);
            contentDispositionHeaderValue.setCharset(charset);
            contentDispositionHeaderValue.setValue(decodeBytes(headerValue, charset));
        } catch (NumberFormatException | StringIndexOutOfBoundsException nex) {
            if (DECODE_PARAMETERS_STRICT) {
                throw new MimeException(nex);
            }
        }

        return contentDispositionHeaderValue;
    }

    private static String decodeBytes(String value, String charset) throws MimeException {
        byte[] b = new byte[value.length()];
        int i = 0;
        int temp = 0;
        for (int bi = 0; i < value.length(); ++i) {
            char c = value.charAt(i);
            if (c == '%') {
                String hex = value.substring(i + 1, i + 3);
                c = (char) Integer.parseInt(hex, 16);
                i += 2;
            }
            b[(bi++)] = (byte) c;
            temp = bi;
        }

        String str;
        try {
            str = new String(b, 0, temp, charset);
        } catch (UnsupportedEncodingException e) {
            throw new MimeException(e);
        }
        return str;
    }

    /**
     * Logical class for representing Content-Disposition header value
     */
    private static class ContentDispositionHeaderValue {
        private String value;
        private String charset;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public String getCharset() {
            return charset;
        }

        public void setCharset(String charset) {
            this.charset = charset;
        }
    }

    public static String decodeDispositionFileName(Map<String, String> contentDispositionParameters) throws MimeException {
        // Refer RFC 2183 'The Content-Disposition Header Field' and
        // RFC 2184 'Parameter Value Character Set and Language Information'
        Set<String> contentDispositionKeySet = contentDispositionParameters.keySet();
        String fileName = null;
        String[] sortedDispositionFileNameKeys = getSortedStringArray(contentDispositionKeySet.toArray());
        StringBuilder valueStr = new StringBuilder();
        for (String sortedDispositionFileNameKey : sortedDispositionFileNameKeys) {
            valueStr.append(contentDispositionParameters.get(sortedDispositionFileNameKey));
        }
        String encodedStr = valueStr.toString();
        if (!encodedStr.isEmpty()) {
            fileName = decodeContentDisposition(encodedStr).getValue();
        }

        return fileName;
    }

    private static String[] getSortedStringArray(Object[] objArray) {
        String[] strArray = new String[objArray.length];
        for (int i = 0; i < objArray.length; i++) {
            strArray[i] = (String) objArray[i];
        }
        Arrays.sort(strArray);
        return strArray;
    }
}
